// render.js

// constructor
var Render = function( pre, params ) {

	var render = this;

	// pre must exist
	if( !pre || pre=='' ) throw( 'illegal pre in render' );

	// this for subfuncs
	render.state = {
		pre:		pre,
		pre_pre:	pre,
		pnt:		params.pnt || null,
		dd:		params.dd || ((state.dd && state.dd[pre]) ? pre : null),
		tbl:		params.tbl || null,
		pkcol:		params.pkcol || null,
		orderBy:	params.orderBy || null,
		dml:		params.dml || null,
		rcds:		[],
		select:		{},
		i:		1,
		r:		{},
		nexting:	false,
		dfltRcd:	params.dfltRcd || {},
		extraNo:	params.extraNo || 0,
		changeFuncs:	{ helper:{}, lookup:{}, validate:{}, post:{}, update:{}, embold:{} }
	};
	// fixed funcs
	render.rowRender = params.rowRender || null;
	render.formRender = params.formRender || null;
	render.contentFunc = params.contentFunc || null;
	// extend methods - _can_ override existent render methods
	if(params) params.forEachProperty( function(param) {
		if( typeof params[param] == 'function' ) render[param] = params[param];
	} );

	// build pre if pnt and not built
	if( render.state.pnt && render.state.pnt.render ) {
		// set up parent environment
		render.state.pnt.idx = render.state.pnt.render.idx();
		render.state.pnt.r = render.state.pnt.render.r();
		render.state.pnt.pkval = render.state.pnt.render.pkval();
		// set pre for self
		render.state.pre = render.state.pnt.render.pre()+'-'+render.state.pnt.idx+'.'+render.state.pre;
	};

	// function handlers
	render.pnt = {
		exists: function() { return (render.state.pnt != null) },
		render: function() { return render.state.pnt.render },
		pre: function() { return render.state.pnt.render.pre() },
		idx: function() { return render.state.pnt.idx },
		r: function() { return render.state.pnt.r },
		rcds: function() { return render.state.pnt.render.rcds() },
		pkcol: function() { return render.state.pnt.render.pkcol() },
		pkval: function() { return render.state.pnt.pkval }
	};

	//-console.log( 'params: '+JSON.stringify(params) );

	// initial rel in state space
	if( state.rel == undefined ) state.rel = {};
	if( state.rel[render.pre()] == undefined ) state.rel[render.pre()] = { orderPri:{}, orderSec:{} };
	// todo: memory leak! for development only!
	//-state.rel[render.pre()].render = render;

	// load records, done
	render.reload(params);


	// post calls - this called in via call - kept here for this
	render.update   = function(e) { console.log( 'render.update' ); Post.update( {e:e, el:e.target, render:render} ); };
	render.changeLast  = function(e) {
		if( Post.pre(e.target) != e.currentTarget.getAttribute('rel') ) return;
		Post.change( {e:e, el:e.target, render:render} );
	};
	// prevents change being called after mouseup has already called change from numeric field
	render.changeFirst  = function(e) {
		if( Post.pre(e.target) != e.currentTarget.getAttribute('rel') ) return;
		// nada - normal
		if( !e.target.hasAttribute('changeValue') ) return;

		// coming from input trigger - block and done
		if( e.target.getAttribute('changeValue')=='input' ) {
			e.target.setAttribute('changeValue',e.target.value);
			return;
		}

		// coming from secondary change trigger - only need to do this once to block natural change trigger
		if( e.target.getAttribute('changeValue')==e.target.value ) {
			//-console.log( 'changefirst stopping propagation' );
			e.stopPropagation();
		}
	
		// changeValue no longer needed	
		e.target.removeAttribute('changeValue');
	};
	render.dlx      = function(e) { Post.dlx( {e:e, el:e.target, render:render} ); };
	render.sortTh  = function(e) {

		var th = e.target;
		var type = th.getAttribute(render.pre()+':sort');

		// type none - forget about it
		if( type == 'none' ) return;

		// type default - set up correctly
		if( type == 'default' ) {

			// TH is empty
			if( !/\w/.test(th.textContent) ) {
				th.setAttribute(render.pre()+':sort','none');
				return;
			}

			// get sort col
			var cols = [];
			util.columnCells( e.target, 0 ).forEach( function(td) {
				[].forEach.call(td.querySelectorAll("[name]"), function(el) {
					if( el instanceof HTMLInputElement && el.type == 'hidden' ) return;
					if( Post.col(el) ) cols.push(Post.col(el));
				});
			} );

			// set sort cols
			if( cols.length ) {
				type = 'col='+cols.join(', ');
				th.setAttribute(render.pre()+':sort',type);
			}

			// todo: if no cols found then content
			else {
				th.setAttribute(render.pre()+':sort','none');
				return;
			}
		}

		//-console.log( 'sort type: '+type );
		var orderBy = type.split('=')[1];

		// no primary - set self as pri asc
		if( state.rel[render.pre()].orderPri.cellIndex == undefined ) {
			state.rel[render.pre()].orderPri = { cellIndex:th.cellIndex, orderBy:orderBy, dir:'asc' };
		}
		// already primary
		else if( state.rel[render.pre()].orderPri.cellIndex == th.cellIndex ) {
			// if acs, make desc
			if( state.rel[render.pre()].orderPri.dir == 'asc' )  state.rel[render.pre()].orderPri.dir = 'desc';
			// if desc, go back to default
			else { 
				state.rel[render.pre()].orderPri = {};
				state.rel[render.pre()].orderSec = {};
			}
		}
		// somebody else primary - make them secondary and self primary asc
		else {
			state.rel[render.pre()].orderSec = state.rel[render.pre()].orderPri;
			state.rel[render.pre()].orderPri = { cellIndex:th.cellIndex, orderBy:orderBy, dir:'asc' };
		}

		//-render.orderBy = cols.join(', ');
		var changed = render.sortRcds();
		if( changed ) {
			var tbody = th.parentNode.parentNode.nextSibling;
			while( tbody.lastChild ) tbody.removeChild( tbody.lastChild );
			render.rewind();
			while( render.next() ) tbody.appendChild( render.rowRender() );
		}
	};
};

// (re)load records - no rendering
Render.prototype.reload = function( params, callback ) {

	var render = this;

	// if only one record passed, make it element of array
	if( params.rcds && !(params.rcds instanceof Array) ) params.rcds = [ params.rcds ];

	// static rcds
	if( params.rcds ) {
		render.state.rcds = params.rcds || [];
		render.state.fks  = params.fks  || {};
		nextstep();
	}

	// pull query
	else if( params.select || render.state.select.dd ) {
	
		// move params.select into state.select 
		params.select.forEachProperty( function(att) { render.state.select[att] = params.select[att]; } );
		render.state.select.dd = render.state.select.dd || render.state.dd || null;

		// RI from pnt/child
		if( render.pnt.exists() )  render.state.select.pntKey = ( render.pnt.pkval() ) ? render.state.pnt.childCol+' = '+render.pnt.pkval() : 'false';

		// dd and pkval not null, pull from server
		if( render.state.dd
		&&( !render.pnt.exists() || render.pnt.pkval() )
		&&( !render.state.select.hasOwnProperty('pkval') || ( render.state.select.pkval!==null && render.state.select.pkval!==undefined ) ) ) {
			new Message( 'select', render.state.select, null,
				function(message) {
					render.state.rcds = message.result.rcds;
					render.state.fks = message.result.fks;
					nextstep(); },
				function(message) {
					nonblockAlert( message.result.message );
			} );
		}
		else	nextstep();
	}
	// use what we have
	else	nextstep();

	// continue on setting up rcds
	function nextstep() {

		// augment dflt rcd
		if( render.state.dd ) {
			state.dd[render.state.dd].cols.forEachProperty( function(c) {
				// do not overrides existent attribute
				if( render.state.dfltRcd.hasOwnProperty(c) ) return;
				// if undefined, do not add col
				if( state.dd[render.state.dd].cols[c].default!==undefined ) return;
				// set to default
				render.state.dfltRcd[c] = state.dd[render.state.dd].cols[c].default;
			} );
			//-console.log( render.pre()+' -  create default record: '+JSON.stringify(render.state.dfltRcd) );
		};

		// extra records
		if( render.state.extraNo ) for( var j=0; j<render.state.extraNo; j++ ) render.extraRow();

		// sort records
		render.sortRcds();

		// set r
		render.rewind();

		// callback
		if( callback ) callback();
	};
};

// sorts rows; returns whether any changes
Render.prototype.sortRcds = function( orderBy ) {

	var render = this;
	// if no dd, forget about it
	if( !state.dd ) return;
	var dd = state.dd[render.state.dd];

	// remove existent sort classes
	Post.querySelectorAll("["+render.pre()+":sort][class^='sort']").forEach( function(th) {
		['sortAsc1','sortAsc2','sortDesc1','sortDesc2'].forEach( function(attr) {
			th.classList.remove(attr);
			th.title = "Click to Sort By "+th.textContent;
		} );
	} );

	// add correct sort class
	Post.querySelectorAll("["+render.pre()+":sort]").forEach( function(th) {
		if( state.rel[render.pre()] && state.rel[render.pre()].orderPri.cellIndex==th.cellIndex ) {
			if( state.rel[render.pre()].orderPri.dir == 'asc') {
				th.classList.add( 'sortAsc1' );
				th.title = "Click to Sort By "+th.textContent+' DESCENDING';
			}
			else {
				th.classList.add( 'sortDesc1' );
				th.title = "Click to Sort by DEFAULT";
			}
		}
		else if( state.rel[render.pre()] && state.rel[render.pre()].orderSec.cellIndex==th.cellIndex ) {
			th.classList.add( (state.rel[render.pre()].orderSec.dir=='asc') ? 'sortAsc2' : 'sortDesc2' );
		}
	} );


	// orderBy from col clicks
	if( !orderBy && state.rel[render.pre()] && state.rel[render.pre()].orderPri.orderBy ) {

		// there may be more than one order by - need to get dir for each
		var orderArr = state.rel[render.pre()].orderPri.orderBy.split(',');
		for( var i=0; i<orderArr.length; i++ ) orderArr[i] += ' '+state.rel[render.pre()].orderPri.dir;
		orderBy = orderArr.join(',');

		// secondary order by
		if( state.rel[render.pre()].orderSec.orderBy ) {
			var orderArr = state.rel[render.pre()].orderSec.orderBy.split(',');
			for( var i=0; i<orderArr.length; i++ ) orderArr[i] += ' '+state.rel[render.pre()].orderSec.dir;
			orderBy += ','+orderArr.join(',');
		}
	}
	// try render, dd orderBy
	if( !orderBy )  orderBy = render.state.orderBy;
	if( !orderBy && dd ) orderBy = dd.orderBy;
	if( !orderBy ) return false;

	// set up orderBy 
	for( var orderArr=[], i=0; i<orderBy.split(',').length; i++ ) {
		var orderCol = orderBy.split(',')[i].trim();
		var orderDir = 'asc';
		if( orderCol.indexOf(' ')>0 ) {
			orderDir = orderCol.split(' ')[1];
			orderCol = orderCol.split(' ')[0];
		}
		//-if( dd.cols[orderCol] == undefined ) throw( "sort error: "+orderCol+" not in dd" );
		if( dd.cols[orderCol] && dd.cols[orderCol].orderCol ) orderCol = dd.cols[orderCol].orderCol;
		//-console.log( "orderBy "+i+": "+orderCol+", "+orderDir );
		orderArr.push( {col:orderCol, dir:orderDir} );
	}

	var changed = false;
	function compare( a, b ) {
		for( var i=0; i<orderArr.length; i++ ) {
			// easy - same is same, regardless of datatype
			if( a[orderArr[i].col] == b[orderArr[i].col] ) continue;
			// todo: all datatypes
			// handle nulls first - they always go at end
			var correct;
			if( a == null ) correct = true;
			else if( b == null ) correct = false;
			else switch( dd.cols[orderArr[i]] && dd.cols[orderArr[i].col].datatype || null ) {
				case 'number':	correct = ( parseFloat(a[orderArr[i].col]) < parseFloat(b[orderArr[i].col]) );
						break;
				default:	correct = ( a[orderArr[i].col] < b[orderArr[i].col] );
			}
			if( ( correct && orderArr[i].dir=='asc' ) || ( !correct && orderArr[i].dir=='desc' ) ) return -1;
			changed = true;
			return 1;
		}
		return 0;
	}

	// user built-in sort
	// break into two arrays: one for new, one for existent
	var newRcds = [], existentRcds = [];
	for( var i=0; i<render.state.rcds.length; i++ ) {
		if( render.state.rcds[i][dd.pkcol] )	existentRcds.push( render.state.rcds[i] );
		else					newRcds.push( render.state.rcds[i] );
	}
	// put blank on bottom of asc; otherwise on bottom
	render.state.rcds = (orderArr[0].dir=='asc') ? existentRcds.sort( compare ).concat( newRcds ) : newRcds.concat( existentRcds.sort( compare ) );
	//-console.log( 'sort changed: '+changed);
	return changed;
}
Render.prototype.extraRow = function( show ) {
	// add default rcd to rcds
	this.state.rcds[this.state.rcds.length] = JSON.parse(JSON.stringify(this.state.dfltRcd));
	// if show
	if( show ) {

		// go to added record
		this.seek(this.state.rcds.length);

		// find last rendered row
		for( var i=0, last=null; i<this.state.rcds.length-1; i++ ) {
			last = Post.querySelector("[tup^='"+pre+":"+i+"']") || last;
		}
		if( !last ) throw( "extraRow: unable to find parent" );

		// add dom - if nothing before and something after, precede last; otherwise follow last
		last.parentNode.insertChild( this.rowRender(), ((!last.previousSibling && last.nextSibling)?last:last.nextSibling) );
	}
}

// constants
// note: boolean xltd to 1/0 before checkboxValues are called
Render.checkboxValues = { 1: '1', 0: '1', t: 't', f: 't', y: 'y', n: 'y', Y: 'Y', N: 'Y' };
Render.checkboxChecks = { 1: true, 0: false, t: true, f: false, y: true, n: false, Y: true, N: false };

// public funcs
Render.prototype.pre = function() { return this.state.pre; };
Render.prototype.idx = function() { return this.state.i; }
Render.prototype.pkcol = function() { return( this.state.pkcol || state.dd && this.state.dd && state.dd[this.state.dd].pkcol ); };
Render.prototype.pkval  = function() { return( this.r(this.pkcol()) || null ); };
Render.prototype.rcds  = function(idx) { return (idx==undefined) ? this.state.rcds : this.state.rcds[idx-1]; };
Render.prototype.r     = function( col ) {
	if( col==undefined )		return this.state.r;
	if( this.state.r.hasOwnProperty(col) )	return this.state.r[col];
	else				return undefined;
};
Render.prototype.fks   = function(pk) { return this.state.fks[pk] };
Render.prototype.dd    = function( col ) {
	if( !state.dd ) return undefined;
	if( !this.state.dd &&( this.pre() && state.dd[this.pre()] ) ) this.state.dd = this.pre();
	if( !this.state.dd &&( this.state.tbl && state.dd[this.state.tbl] ) ) this.state.dd = this.state.tbl;
	if( !this.state.dd || !state.dd[this.state.dd] ) return undefined;
	if( !col ) return state.dd[this.state.dd];
	return state.dd[this.state.dd].cols[col];
};
Render.prototype.rcds = function() { return this.state.rcds };
Render.prototype.genName = function( name ) {
	var newName = this.pre() + ':' + this.idx() + ':' + name;
	//-console.log( 'genName('+name+') = '+newName );
	return newName;
};
Render.prototype.next = function() {
	if( !this.state.rcds ) return false;
	if( this.state.i >= this.state.rcds.length ) {
		// must keep r valid - put at first record
		// todo: default
		this.state.r = this.state.dfltRcd;
		return false;
	}
	if( !this.state.nexting ) this.state.i = 0;
	this.state.nexting = true;
	this.state.r = this.state.rcds[this.state.i];
	this.state.i++;
	// skip over records nulled from delete
	if( this.state.r==null ) return this.next();
	return true;
};
Render.prototype.rewind = function() {
	this.state.nexting = false;
	this.state.i = 0;
	this.state.r = (this.state.rcds.length>0) ? this.state.rcds[0] : this.state.dfltRcd;
};
Render.prototype.seek = function(i) {
	this.rewind();
	for( var j=0; j<i; j++ ) this.next();
};
Render.prototype.rows = function() {
	this.rewind();
	var f = document.createDocumentFragment();
	while( this.next() ) f.appendChild( this.rowRender() );
	return f;
}

// pre dom
Render.prototype.dom = function() { 
	var dom = Post.querySelector("[rel='"+this.pre()+"']");
	if( !dom ) throw( 'pre_dom not found' );
	return dom;
};
// return item
Render.prototype.domItem = function(i,col) {
	return Post.querySelector( "[name='" +this.pre()+':'+i+':'+col+"']" );
};
// return item value
Render.prototype.domItemValue = function(i,col) { return Post.colValue( this.domItem( i, col ) ); };
// set item value
Render.prototype.domItemSetValue = function(i,col,val) { Post.colSetValue( this.domItem( i, col ), val ); };
// return tups
Render.prototype.domTups = function() {
	var results = Post.querySelectorAll( "[tup^='"+this.pre()+":']" );
	return results;
};
Render.prototype.domLastTup = function() {
	var tups = this.domTups();
	return (tups) ? tups[tups.length-1] : null;
};


// = = = = dom factory = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

// primary parser / generator
Render.prototype.base = function( tag ) {
	// element itself
	//-console.log( 'this.'+tag+', pre='+this.pre );
	var element = (tag=='fragment') ? document.createDocumentFragment() : document.createElement(tag);
	var callee = element;

	// todo: redo this
	//=default env
	//=if( render.env[tag] ) addElement( render.env[tag] );
	// arguments
	for( var i=1; i<arguments.length; i++ ) this.addElement(callee,element,arguments[i]);
	//-console.log( '- - - - return render.'+tag+': '+element );
	return element;
}

Render.prototype.addElement = function( callee, element, arg ) {

	var render = this;

	// if null, ignore
	if( arg === null || arg === undefined ) return;

	// if fragment, append
	else if( arg instanceof DocumentFragment ) {
		element.appendChild( arg );
	}

	// array - recur
	else if( arg instanceof Array ) {
		//-console.log( tag+' adding array: '+arg );
		for( var j=0; j<arg.length; j++ )  this.addElement( callee, element, arg[j]);
	}

	// if ol or ul, make object into li
	else if( ( element instanceof HTMLOListElement || element instanceof HTMLUListElement ) && typeof arg == 'object' ) {
		//-console.log( "ul object: "+arg );
		if( arg instanceof HTMLLIElement )	element.appendChild( arg );
		else					element.appendChild( render.li( arg ) );
	}

	// non-object - deal with it a text
	else if( typeof arg != 'object' ) {

		// boolean and numeric types
		if( typeof arg == 'boolean' )			this.addPair( element, 'value', ((arg)?'true':'false') ); 
		else if( typeof arg == 'number' )		this.addPair( element, 'value', ''+arg ); 

		// meta markers not needed argument
		else if( arg == 'rel' )				this.addPair( element, 'rel', null );
		else if( arg == 'tup' )				this.addPair( element, 'tup', null );

		// type markers
		else if( arg.substr(0,1)=='`' )			this.addPair( element, 'value', arg.substr(1) ); 
		else if( arg.substr(0,1)=='#' )			this.addPair( element, 'id', arg.substr(1) );
		else if( arg.substr(0,1)==':' )			this.addPair( element, 'name', arg.substr(1) );
		else if( arg.substr(0,1)=='.' )			this.addPair( element, 'class', arg.substr(1) );
		else if( arg.substr(0,2)=='=>' )		this.addPair( element, 'colValue', arg.substr(2) );
		else if( arg.substr(0,1)=='=' )			this.addPair( element, 'value', arg.substr(1) );

		// data col name - no name and code syntax - must be entry to happen automatically
		else if( ( render.r(arg)!==undefined || render.dd(arg)!==undefined  ) && Render.editable(element) ) {
								this.addPair( element, 'att', arg );
		}

		// truth/untruth tags
		else if( state.dd['~'].truthies.has(arg) ) 	this.addPair( element, arg, true );
		else if( arg.substr(0,2)=='no' && state.dd['~'].truthies.has(arg.substr(2)) )
								this.addPair( element, arg.substr(2), false );

		// checkbox/radio value / checked
		else if( element.hasOwnProperty('checked') && ['0','1','t','f'].has(arg) ) this.addPair( element, 'value',arg);

		// select: add as option
		// deprecated - pulling in non-options
		else if( false && element instanceof HTMLSelectElement ) {	// whether value:label, or only value = label
						match_arr = arg.match( "^(.*):(.*)$");
						if( match_arr ) {
							selected = (match_arr[1]==Post.colDataValue(element));
							element.appendChild( render.option( match_arr[1], match_arr[2], selected ) );
						}
						else  	element.appendChild( render.option( arg, arg, arg==Post.colDataValue(element) ) );
		}

		// colon markers
		else if( match_arr = arg.match(/^([a-z_]\w+):(.+)/) )	this.addPair( element, match_arr[1], match_arr[2] );
		else if( match_arr = arg.match(/^([a-z_]+\.\w+):(.+)/) )	this.addPair( element, match_arr[1], match_arr[2] );
		
		// input, textarea value
		else if( Render.editable(element) )	this.addPair( element, 'value', arg );

		// add as text node
		else					this.addPair( element, 'text', arg );
	}
	
	// html element - append
	else if( arg instanceof Element )		 element.appendChild( arg );
	
	// non-html object
	else arg.forEachProperty( function(attr) { if( arg.hasOwnProperty(attr) )  render.addPair( element, attr, arg[attr] ); } );
};


Render.prototype.addPair = function( element, attr, val ) {

	var render = this;
	var match_arr;

	// check for undefined
	//-if( attr === undefined || val === undefined ) console.log( 'Render possible error: '+element+' '+attr+'='+val );
	//-if( val !== null && typeof val == 'object' ) console.log( 'Rendering object: '+element+' '+attr+'='+JSON.stringify(val) );

	// enum - only other object
	if( attr == 'enum' ) {

		// if input, then use for verify
		if( element instanceof HTMLInputElement ) {
			element.setAttribute('enum',((typeof val == 'string')?val:JSON.stringify(val)));
		}

		// pull out - for now, select on object only
		else if( !(element instanceof HTMLSelectElement) ) throw( 'enum on non-select' );

		// from array - incoming from fks
		else if( val instanceof Array ) for( var i=0; i<val.length; i++ ) {
			element.appendChild( render.option( val[i].id, val[i].label, val[i].id==Post.colDataValue(element) ) );
		}

		// from object
		else if( typeof val == 'object' ) {
			val.forEachProperty( function(a) { element.appendChild( render.option( val[a], a ) ); } );
		}
		// from string
		else {
			var options = val.split(';');
			for( var i=0; i<options.length; i++ ) {
				var pair = options[i].split(':');
				if( pair.length == 2 )	element.appendChild( render.option( pair[1], pair[0] ) );
				else			element.appendChild( render.option( pair[0], pair[0] ) );
			}
		}
	}

	/*-console.log('Pair: '+attr+', '+val );
	else if( val && typeof val == "object" ) {
		console.log( 'ERROR: unexpected object!! attr='+attr+', val='+JSON.stringify(val) );
		this.addPair( element, attr, JSON.stringify(val) );
		return;
	}
	*/

	// rel meta, including update
	// note: this rel only
	else if( attr == 'rel' ) {
		element.combineAttribute('rel',render.pre());
		//-console.log( 'adding pre trigger' );
		//render.addPair( element, 'event.change', 'change' );
		element.addEventListener('change',render.changeFirst,true);

		// chrome types - put in at rel level to ensure they are lower than input change triggers
		// except for change, which prevents change from being called again after mouseup change
		if( util.isChrome() ) {
			element.addEventListener('input',util.chrome.input,true);
			element.addEventListener('mouseup',util.chrome.mouseup,true);
			element.addEventListener('change',render.changeLast,false);
		}
		var prefix = render.pre()+':';
		for( var i=0; i<state.dd['~'].render.rel.length; i++ ) {
			var a = state.dd['~'].render.rel[i];
			if( render.state[a]!==undefined && render.state[a]!==null ) element.setAttribute( prefix+a, render.state[a] );
		}
	}
	// tup meta, including key
	else if( attr == 'tup' ) {
		// only pkval right now
		//-console.log( 'tup: pkcol='+render.pkcol() );
		element.combineAttribute('tup',render.pre()+':'+render.idx());
		if( render.pkcol() ) element.setAttribute(render.genName('pkval'),render.r(render.pkcol())||'');
	}

	// att meta, including col
	else if( attr == 'att' ) {

		var c;

		// set name - note: name attribute is sync'd with name property
		element.setAttribute('name',render.genName(val));

		// add defaults - input text entry
		if( c = render.dd(val) ) {

			// dd rel/att
			c.forEachProperty( function(a) {

				// attributes wish to add: render properties or input properties
				if( state.dd['~'].render.att.has(a) ||  typeof element[a] !== 'undefined' ||  a.indexOf('.')>0 ) {
					if( Render.editable(element) || state.dd['~'].render.noneditable.has(a) ) render.addPair( element, a, c[a] );
				}
			});
		}

		// set value
		render.addPair( element, 'colValue', val );
	}

	// att meta - do nothing - will be added normally as properties or attributes
	
	// tup meta
	else if( element.getAttribute('tup')==render.pre()+':'+render.idx() && state.dd['~'].render.tup.has(attr) ) {
		element.setAttribute( render.pre()+':'+render.idx()+':'+attr, val );
	}
	// rel meta - some triggers as well
	else if( element.getAttribute('rel')==render.pre() && state.dd['~'].render.rel.has(attr) ) {
		element.setAttribute( render.pre()+':'+attr, val );
	}

	// text - text element
	else if( attr == 'text' ) {
		if( element instanceof HTMLOListElement || element instanceof HTMLUListElement ) {
				if( val.length > 0 ) element.appendChild( render.li(val) );
		}
		// error trap - no text to normal input element
		else if( Render.editable(element) ) throw( 'appending text child to '+element );

		// append normal text
		else element.appendChild( document.createTextNode(val) );
	}

	// class: if has spaces then split - add via classList.add
	else if( attr=='class' || attr=='a.class' ) {
		for( var arr=val.split(' '), i=0; i<arr.length; i++ ) element.classList.add(arr[i]);
	}
	
	// className: resets entire className
	else if( attr == 'className' ) {
		element.className = val;
	}
	
	// style
	else if( match_arr = attr.match(/^(style|s)\.(.*)/ ) ) {
		element.style[match_arr[2]] = val;
	}

	// sort - if string, then (for now) must be Post public func with no args
	else if( attr == 'sort' ) {
		element.setAttribute(render.pre()+":sort",val);
		element.title = "Click to Sort By "+element.textContent;
		element.addEventListener('click',render.sortTh,false);
	}

	// event - if string, must scope to render, Render, Post, or window; dot scoping allowed
	else if( match_arr = attr.match(/^(change|c)\.(.*)/) ) {
		// passed func
		if( typeof val == 'function' ) {
			var fnStr = 'f'+Math.Random();
			render[fnStr] = val;
			element.setAttribute('change.'+match[1],fnStr)
		}
		// reference to func
		else	element.setAttribute('change.'+match[1],val)
	}
	else if( match_arr = attr.match(/^(event|e)\.(.*)/) ) {
		//-console.log( 'adding trigger on '+match_arr[2] );
		if( val === null ) return;
		// bubble up/down
		var eventType = match_arr[2];
		var bubble = false;
		if( typeof val == 'string' && val[0]=='+' ) { val = val.substr(1); bubble = true; }
		if( typeof val == 'string' && val[0]=='-' ) { val = val.substr(1); bubble = false; }
		//-console.log( attr+' event: '+(typeof val)+((element.name)?' for '+element.name:' for '+element) );
		if( typeof val == 'function' ) {
			//-console.log( eventType+': add as function' );
			element.addEventListener(eventType,val,bubble);
		}
		else if( render[val]!= undefined && typeof render[val]=='function' )  {
			//-console.log( 'add as render-based func' );
			element.addEventListener(eventType,function(e) { render[val].call(render,e) },bubble);
		}
		else if( window.Render[val]!= undefined && typeof window.Render[val]=='function' ) {
			//-console.log( 'add as Render-based func' );
			element.addEventListener(eventType,window.Render[val],bubble);
		}
		else if( window.Post[val]!= undefined && typeof window.Post[val]=='function' )  {
			//-console.log( 'add as Post-based func' );
			element.addEventListener( eventType, window.Post[val] );
		}
		// can use dot notation if window function, eg, "maintenance.employees.sync"
		else {
			var parts = val.split('.');
			for( var i=0, func=window; i<parts.length; i++ ) {
				func = func[parts[i]];
				if( func == undefined ) throw( 'render cannot determine event function: '+val );
			}
			if( typeof func != 'function' ) throw( 'render event is not a function: '+val );
			//-console.log( 'add as window-based func' );
			element.addEventListener( eventType, func );
		}
	}

	// attributes
	else if( match_arr = attr.match(/^(attribute|a)\.(.*)/) ) {
		element.setAttribute( match_arr[2], val );

	}

	// value - to set empty value use '' (do not use null)
	else if( attr == 'value' ) {
		//-console.log( 'value: '+val );
		if( val==undefined || val==null ) return;
		if( val == 'validate' ) throw( 'we should not be here' );
		if( element.hasOwnProperty('value') ) element.value = val;
		else element.textContent = val;
		element.setAttribute('dataValue',val);
	}

	// colValue
	else if( attr == 'colValue' ) {
		// pull from current record
		var colVal = render.r(val);
		// if not in current record, pull from dd default
		if( colVal==undefined && render.dd(val)!==undefined ) colVal = render.dd(val).default;
		//-console.log( 'colValue: '+val+' = '+colVal );
		// if defined anywhere, set
		if( colVal !== undefined ) render.addPair( element, 'value', colVal );
	}

	/* should be automatic now - validate - put on non-cols, who cares?  cascade down
	else if( attr == 'validate' ) {
		element.setAttribute('validate',val );
		if( val ) element.addEventListener('change',Post.colValidate,true);
	}
	*/

	// properties - if not attribute
	else if( typeof element[attr] != 'undefined' ) element[attr] = val;

	// if style, add each element`
	else if( attr == 'style' ) {
		val.split(';').forEach( function(v) { element.style[v.split('=')[0]] = v.split('=')[1]; } );
	}

	/* changeEvents
	else if( state.dd['~'].changeEvents.has(attr) && ( ![true,false,'none','all','true','false'].has(val) || typeof val == 'function' ) ) {
		// passed func
		if( typeof val == 'function' ) {
			var fnStr = 'f'+Math.Random();
			render[fnStr] = val;
			element.setAttribute('changeFunc.'+attr,fnStr)
		}
		// reference to func
		else	element.setAttribute('changeFunc.'+attr,val)
	}
	*/

	// otherwise attribute
	else element.setAttribute( attr, (val !== null && typeof val == 'object') ? JSON.stringify(val) : val );

}; // end of addPair

// base types
Render.prototype.div = function() { return this.base('div',Array.prototype.slice.call(arguments)) };
Render.prototype.span = function() { return this.base('span',Array.prototype.slice.call(arguments)) };
Render.prototype.f = function() { return this.base('fragment',Array.prototype.slice.call(arguments)) };
Render.prototype.frag = function() { return this.base('fragment',Array.prototype.slice.call(arguments)) };
Render.prototype.table = function() { return this.base('table',Array.prototype.slice.call(arguments)) };
Render.prototype.thead = function() { return this.base('thead',Array.prototype.slice.call(arguments)) };
Render.prototype.tbody = function() { return this.base('tbody',Array.prototype.slice.call(arguments)) };
Render.prototype.tfoot = function() { return this.base('tfoot',Array.prototype.slice.call(arguments)) };
Render.prototype.tr = function() { return this.base('tr',Array.prototype.slice.call(arguments)) };
Render.prototype.td = function() { return this.base('td',Array.prototype.slice.call(arguments)) };
Render.prototype.th = function() { return this.base('th',Array.prototype.slice.call(arguments)) };
// ths - many th, each represented as a string
Render.prototype.ths = function() { 
	var frag = document.createDocumentFragment();
	for( var i=0; i<arguments.length; i++ ) {
		if( arguments[i] === null ) continue;
		var th = document.createElement('th');
		var match_arr = arguments[i].match( /^(?:(\d*):|)(.*?)(?:=([\w,]*)|)$/);
		th.appendChild( document.createTextNode(match_arr[2]) );
	        if( match_arr[1]!==undefined ) th.colSpan = match_arr[1];
		this.addPair( th, 'sort', (match_arr[3]!==undefined) ? 'col='+match_arr[3] : 'default' );
		frag.appendChild(th);
	}
	return frag;
};
Render.prototype.form = function() { return this.base('form',Array.prototype.slice.call(arguments)) };
Render.prototype.input = function() { return this.base('input',Array.prototype.slice.call(arguments)) };
Render.prototype.text = function() { return this.base('input',Array.prototype.slice.call(arguments),'type:text') };
Render.prototype.date = function() { return this.base('input',Array.prototype.slice.call(arguments),'type:date') };
Render.prototype.datetime = function() { return this.base('input',Array.prototype.slice.call(arguments),'type:datetime') };
Render.prototype.email = function() { return this.base('input',Array.prototype.slice.call(arguments),'type:email') };
Render.prototype.month = function() { return this.base('input',Array.prototype.slice.call(arguments),'type:month') };
Render.prototype.number = function() { return this.base('input',Array.prototype.slice.call(arguments),'type:number') };
Render.prototype.tel = function() { return this.base('input',Array.prototype.slice.call(arguments),'type:tel') };
Render.prototype.search = function() { return this.base('input',Array.prototype.slice.call(arguments),'type:search') };
Render.prototype.submit = function() { return this.base('input',Array.prototype.slice.call(arguments),'type:submit') };
Render.prototype.time = function() { return this.base('input',Array.prototype.slice.call(arguments),'type:time') };
Render.prototype.url = function() { return this.base('input',Array.prototype.slice.call(arguments),'type:url') };
Render.prototype.password = function() { return this.base('input',Array.prototype.slice.call(arguments),'type:password') };
Render.prototype.hidden = function() { return this.base('input',Array.prototype.slice.call(arguments),'type:hidden') };
Render.prototype.hiddens = function() {
	var frag = document.createDocumentFragment();
	for( var i=0; i<arguments.length; i++ ) frag.appendChild( this.hidden(arguments[i]) );
	return frag; }
Render.prototype.checkbox = function() { return this.base('input','value:0',Array.prototype.slice.call(arguments),'type:checkbox') };
Render.prototype.textarea = function() { return this.base('textarea',Array.prototype.slice.call(arguments)) };
Render.prototype.i = function() { return this.base('i',Array.prototype.slice.call(arguments)) };
Render.prototype.b = function() { return this.base('b',Array.prototype.slice.call(arguments)) };
Render.prototype.s = function() { return this.base('s',Array.prototype.slice.call(arguments)) };
Render.prototype.code = function() { return this.base('code',Array.prototype.slice.call(arguments)) };
Render.prototype.label = function() { return this.base('label',Array.prototype.slice.call(arguments)) };
Render.prototype.fieldset = function() { return this.base('fieldset',Array.prototype.slice.call(arguments)) };

Render.prototype.ul = function() { return this.base('ul',Array.prototype.slice.call(arguments)) };
Render.prototype.ol = function() { return this.base('ol',Array.prototype.slice.call(arguments)) };
Render.prototype.li = function() { return this.base('li',Array.prototype.slice.call(arguments)) };

Render.prototype.h1 = function() { return this.base('h1',Array.prototype.slice.call(arguments)) };
Render.prototype.h2 = function() { return this.base('h2',Array.prototype.slice.call(arguments)) };
Render.prototype.h3 = function() { return this.base('h3',Array.prototype.slice.call(arguments)) };
Render.prototype.h4 = function() { return this.base('h4',Array.prototype.slice.call(arguments)) };

Render.prototype.br = function() { return this.base('br',Array.prototype.slice.call(arguments)) };
Render.prototype.hr = function() { return this.base('hr',Array.prototype.slice.call(arguments)) };

Render.prototype.nbsp = function() { return document.createTextNode(' \u00a0 '); };

//~Render.prototype.text = function() { return document.createTextNode( Array.prototype.slice.call(arguments).join(' ') ) };

// specialty types
Render.prototype.button = function( click ) { return this.base( 'button', {'event.click':click}, Array.prototype.slice.call(arguments,1) ); };
Render.prototype.a = function( href, click ) {
	if( arguments.length==1 ) return this.base( 'a', {href:href,'event.click':null} );
	else			  return this.base( 'a', {href:href,'event.click':click}, Array.prototype.slice.call(arguments,2) );
}

// todo: radioSet
Render.prototype.radio = function() { return this.base('input',{type:'radio'},Array.prototype.slice.call(arguments)) };

// select / option
Render.prototype.select = function() { return this.base('select',Array.prototype.slice.call(arguments)) };
Render.prototype.option = function( value, label, selected ) {
	var o = document.createElement('option');
	o.value = value;
	o.appendChild( document.createTextNode(label) );
	if( selected ) o.selected = true;
	return o;
};

// not avail on chrome yet
Render.prototype.datalist = function( id, args ) {
	var dl = document.createElement('datalist');
	for( var i=1; i<arguments.length; i++ ) {
		dl.appendChild( document.createElement('option') );
		dl.lastChild.textContent = arguments[i];
	}
	return dl;
}

// dummy render
var E = new Render('E',{});

// replace content of element; args are HTMLElements and stringifiable values
Render.replaceAll = function(el) {
	el.innerHTML = '';
	//=while( el.lastChild ) el.removeChild( el.lastChild );
	//-for( var i=el.children.length; i; i-- ) el.removeChild( el.children[i-1] );
	return Render.appendAll(el,Array.prototype.slice.call(arguments,1));
}
// replace content of element; args are HTMLElements and stringifiable values
Render.appendAll = function(el) {
	for( var i=1; i<arguments.length; i++ ) {
		// if passed array, either HTML or text
		if( arguments[i] instanceof Array ) for( var j=0; j<arguments[i].length; j++ ) {
			if( arguments[i][j] instanceof HTMLElement ) el.appendChild( arguments[i][j] );
			else el.appendChild( document.createTextNode( arguments[i][j] ) );
		}
		// HTML or frag
		else if( arguments[i] instanceof HTMLElement || arguments[i] instanceof DocumentFragment ) el.appendChild( arguments[i] );
		// text
		else el.appendChild( document.createTextNode( arguments[i] ) );
	}
	return el;
}

// wether an item is editable
// todo: ignoring for the while contentEditable
Render.editable = function( el ) {
	return( el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement
		|| el instanceof HTMLInputElement && !['submit','button','reset'].has(el.type) );
}


Render.dlx    = function(e) { Post.dlx( {e:e, el:e.target} ); };
Render.update = function(e) { Post.update( {e:e, el:e.target} ); };
Render.change = function(e) { Post.change( {e:e, el:e.target} ); };

